import { assign } from '@ember/polyfills';
import { assert } from '@ember/debug';
import ApplicationAdapter from '../application';
import { allSettled } from 'rsvp';
import { addToArray } from 'vault/helpers/add-to-array';
import { removeFromArray } from 'vault/helpers/remove-from-array';

export default ApplicationAdapter.extend({
  namespace: 'v1',
  pathForType() {
    assert('Generate the url dynamically based on role type', false);
  },

  urlFor(backend, id, type = 'dynamic') {
    let role = 'roles';
    if (type === 'static') {
      role = 'static-roles';
    }
    let url = `${this.buildURL()}/${backend}/${role}`;
    if (id) {
      url = `${this.buildURL()}/${backend}/${role}/${id}`;
    }
    return url;
  },

  staticRoles(backend, id) {
    return this.ajax(this.urlFor(backend, id, 'static'), 'GET', this.optionsForQuery(id));
  },

  dynamicRoles(backend, id) {
    return this.ajax(this.urlFor(backend, id), 'GET', this.optionsForQuery(id));
  },

  optionsForQuery(id) {
    let data = {};
    if (!id) {
      data['list'] = true;
    }
    return { data };
  },

  fetchByQuery(store, query) {
    const { backend, id } = query;
    return this.ajax(this.urlFor(backend, id), 'GET', this.optionsForQuery(id)).then(resp => {
      resp.id = id;
      resp.backend = backend;
      return resp;
    });
  },

  queryRecord(store, type, query) {
    const { backend, id } = query;
    const staticReq = this.staticRoles(backend, id);
    const dynamicReq = this.dynamicRoles(backend, id);

    return allSettled([staticReq, dynamicReq]).then(([staticResp, dynamicResp]) => {
      if (!staticResp.value && !dynamicResp.value) {
        // Throw error, both reqs failed
        throw dynamicResp.reason;
      }
      // Names are distinct across both types of role,
      // so only one request should ever come back with value
      let type = staticResp.value ? 'static' : 'dynamic';
      let successful = staticResp.value || dynamicResp.value;
      let resp = {
        data: {},
        backend,
        secret: id,
      };

      resp.data = assign({}, resp.data, successful.data, { backend, type, secret: id });

      return resp;
    });
  },

  query(store, type, query) {
    const { backend } = query;
    const staticReq = this.staticRoles(backend);
    const dynamicReq = this.dynamicRoles(backend);

    return allSettled([staticReq, dynamicReq]).then(([staticResp, dynamicResp]) => {
      let resp = {
        backend,
        data: { keys: [] },
      };

      if (staticResp.reason && dynamicResp.reason) {
        // both failed, throw error
        throw dynamicResp.reason;
      }
      // at least one request has data
      let staticRoles = [];
      let dynamicRoles = [];

      if (staticResp.value) {
        staticRoles = staticResp.value.data.keys;
      }
      if (dynamicResp.value) {
        dynamicRoles = dynamicResp.value.data.keys;
      }

      resp.data = assign(
        {},
        resp.data,
        { keys: [...staticRoles, ...dynamicRoles] },
        { backend },
        { staticRoles, dynamicRoles }
      );

      return resp;
    });
  },

  async _updateAllowedRoles(store, { role, backend, db, type = 'add' }) {
    const connection = await store.queryRecord('database/connection', { backend, id: db });
    let roles = [...connection.allowed_roles];
    const allowedRoles = type === 'add' ? addToArray([roles, role]) : removeFromArray([roles, role]);
    connection.allowed_roles = allowedRoles;
    return connection.save();
  },

  async createRecord(store, type, snapshot) {
    const serializer = store.serializerFor(type.modelName);
    const data = serializer.serialize(snapshot);
    const roleType = snapshot.attr('type');
    const backend = snapshot.attr('backend');
    const id = snapshot.attr('name');
    const db = snapshot.attr('database');
    try {
      await this._updateAllowedRoles(store, {
        role: id,
        backend,
        db: db[0],
      });
    } catch (e) {
      throw new Error('Could not update allowed roles for selected database. Check Vault logs for details');
    }

    return this.ajax(this.urlFor(backend, id, roleType), 'POST', { data }).then(() => {
      // ember data doesn't like 204s if it's not a DELETE
      return {
        data: assign({}, data, { id }),
      };
    });
  },

  async deleteRecord(store, type, snapshot) {
    const roleType = snapshot.attr('type');
    const backend = snapshot.attr('backend');
    const id = snapshot.attr('name');
    const db = snapshot.attr('database');
    await this._updateAllowedRoles(store, {
      role: id,
      backend,
      db: db[0],
      type: 'remove',
    });

    return this.ajax(this.urlFor(backend, id, roleType), 'DELETE');
  },

  async updateRecord(store, type, snapshot) {
    const serializer = store.serializerFor(type.modelName);
    const data = serializer.serialize(snapshot);
    const roleType = snapshot.attr('type');
    const backend = snapshot.attr('backend');
    const id = snapshot.attr('name');

    return this.ajax(this.urlFor(backend, id, roleType), 'POST', { data }).then(() => data);
  },
});
